package com.trade.data.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.trade.data.dao.entity.PredictInfo;
import com.trade.data.dao.entity.TradingData;
import com.trade.data.dao.service.PredictInfoService;
import com.trade.data.dao.service.TradingDataService;
import com.trade.data.dto.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
public class CoinMarketManager {
    @Autowired
    TradingDataService tradingDataService;
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    PredictInfoService predictInfoService;
    public List<List> getBinanceQuotes(String symbol, String interval){
        StopWatch watch = new StopWatch();
        watch.start();
        String url = String.format("https://api.binance.com/api/v3/klines?symbol=%s&interval=%s", symbol, interval);
        String response = HttpUtil.get(url);
        watch.stop();
        if(!isJsonArrayString(response)){
            return new ArrayList<>();
        }
        log.info("symbol {} interval {}  getBinanceQuotes info cost:{} ms",symbol,interval,watch.getTotalTimeMillis());
        return JSONUtil.parseArray(response).toList(List.class);
    }
    public List<List> getH2Quotes(String symbol, String interval){
        StopWatch watch = new StopWatch();
        watch.start();
        LambdaQueryWrapper<TradingData> tradingDataQuery = new LambdaQueryWrapper();
        tradingDataQuery.eq(TradingData::getCurrencyName,symbol);
        tradingDataQuery.eq(TradingData::getPeriodDimension,interval);
        List<TradingData> listResult = tradingDataService.list(tradingDataQuery);
        watch.stop();
        log.info("symbol {} interval {}  getH2Quotes size :{} cost:{} ms",symbol,interval,listResult.size(),watch.getTotalTimeMillis());
        if(CollectionUtil.isEmpty(listResult)){
            return new ArrayList<>();
        }
        List<List> listOfLists = new ArrayList<>();
        listResult.forEach(k->{
            // 创建第一个列表并添加一些元素
            List firstList = new ArrayList<>();
            firstList.add(k.getTradeDate().getTime());
            firstList.add(k.getOpeningPrice());
            firstList.add(k.getHighestPrice());
            firstList.add(k.getLowestPrice());
            firstList.add(k.getClosingPrice());
            firstList.add(k.getVolume());
            firstList.add(k.getClosingTime().getTime());
            firstList.add(k.getTurnover());
            firstList.add(k.getTransactionCount());
            firstList.add(k.getActiveBuyVolume());
            firstList.add(k.getActiveBuyTurnover());
            // 将两个列表添加到包含多个列表的List中
            listOfLists.add(firstList);
        });
        return listOfLists;
    }


    public void saveCoinQuotes(CoinMarketDto coinMarketDto) {
        List<List> quotes = this.getBinanceQuotes(coinMarketDto.getSymbol(), coinMarketDto.getInterval());
        List<CoinQuoteDto> collectCoinQuoteDto = transantObject(coinMarketDto,quotes);
        List<TradingData> tradingData = BeanUtil.copyToList(collectCoinQuoteDto, TradingData.class);
        tradingDataService.saveOrUpdateBatch(tradingData);
        log.info("save marketData amount :{}",tradingData.size());

    }

    public List<CoinQuoteDto> transantObject(CoinMarketDto coinMarketDto,List<List> quotes){
        List<CoinQuoteDto> collectCoinQuoteDto = quotes.stream().filter(k->k!=null).map(row -> {
            CoinQuoteDto tradingData = new CoinQuoteDto();
            // 设置 CoinQuoteDto 的各个字段值
            tradingData.setId(coinMarketDto.getSymbol()+"-"+coinMarketDto.getInterval()+"-"+row.get(0).toString()+"-"+row.get(6).toString());
            tradingData.setTradeDate( Date.from(Instant.ofEpochMilli(Long.parseLong(row.get(0).toString()))));              // 开盘时间
            tradingData.setOpeningPrice((new BigDecimal(row.get(1).toString())));     // 开盘价
            tradingData.setHighestPrice(new BigDecimal(row.get(2).toString()) );     // 最高价
            tradingData.setLowestPrice(new BigDecimal(row.get(3).toString()) );      // 最低价
            tradingData.setClosingPrice(new BigDecimal(row.get(4).toString()) );     // 收盘价
            tradingData.setVolume(new BigDecimal(row.get(5).toString()) );              // 交易量
            tradingData.setClosingTime(Date.from(Instant.ofEpochMilli(Long.parseLong(row.get(6).toString()))) );            // 收盘时间
            tradingData.setTurnover(new BigDecimal(row.get(7).toString()) );         // 成交额
            tradingData.setTransactionCount(Integer.parseInt(row.get(8).toString()) );    // 成交笔数
            tradingData.setActiveBuyVolume(new BigDecimal(row.get(9).toString()));     // 主动买入成交量
            tradingData.setActiveBuyTurnover(new BigDecimal(row.get(10).toString()) );// 主动买入成交额
            tradingData.setCurrencyName(coinMarketDto.getSymbol());
            tradingData.setPeriodDimension(coinMarketDto.getInterval());

            return tradingData;// 假设固定为 1D
        }).collect(Collectors.toList());
        return collectCoinQuoteDto;
    }


    /**
     * 获取币安有合约的usdt交易对币种信息
     */
    public List<String> getUsdtContracts() {
        // 构建请求URL
        String url = "https://fapi.binance.com/fapi/v1/exchangeInfo";
        String response = HttpUtil.get(url);
        ResponseEntity<BinanceExchangeInfoResponse> forEntity = restTemplate.getForEntity(url, BinanceExchangeInfoResponse.class);
        BinanceExchangeInfoResponse body = forEntity.getBody();
        return body.getSymbols().stream().map(k->k.getSymbol()).collect(Collectors.toList());
    }

    public boolean isJsonArrayString(String str) {
        if (str == null) {
            return false;
        }
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            objectMapper.readTree(str);
            return objectMapper.readTree(str).isArray();
        } catch (Exception e) {
            return false;
        }
    }

    public void backtest(BacktestDto backtestDto) {
        List<PredictInfoDto> result = new ArrayList<>();
        //回测某个时间点的数据
        List<String> selectedData = backtestDto.getSelectedData();
        String batchId = IdUtil.getSnowflakeNextIdStr();
        selectedData.stream().forEach(k->{
            singleBackTest(k,backtestDto.getModel(),result,batchId);
        });
        List<PredictInfo> predictInfos = BeanUtil.copyToList(result, PredictInfo.class);
        predictInfoService.saveBatch(predictInfos);
    }

    private void singleBackTest(String kindId, String model,List<PredictInfoDto> result, String batchId) {
        //查询对应的历史数据
        LambdaQueryWrapper<TradingData> tradingDataQuery = new LambdaQueryWrapper();
        String[] split = kindId.split("-");
        tradingDataQuery.eq(TradingData::getCurrencyName,split[0]);

        // 将Unix时间戳转换为Timestamp
        Timestamp closingTime = new Timestamp(Long.parseLong(split[3]));
        tradingDataQuery.lt(TradingData::getClosingTime,closingTime);
        tradingDataQuery.apply("BINARY PERIOD_DIMENSION = {0}", split[1]);
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        List<TradingData> listResult = tradingDataService.list(tradingDataQuery);
        stopWatch.stop();
        log.info("查询回测数据耗时ms{}",stopWatch.getTotalTimeMillis());
        if(CollectionUtil.isEmpty(listResult)){
            //回测失败
            return;
        }
        TradingData tradingData = new TradingData();
        Optional<TradingData> first = listResult.stream().filter(k -> Objects.equals(k.getId(), kindId)).findFirst();
        if(first.isPresent()){
            tradingData = first.get();
        }
        PredictModule predictModule = new PredictModule();
        predictModule.setId(kindId);
        predictModule.setModelName(model);
        List<PredictModule.CoinQuoteDtoPython> coinQuoteDtoPythons = BeanUtil.copyToList(listResult, PredictModule.CoinQuoteDtoPython.class);
        predictModule.setDataList(coinQuoteDtoPythons);
        //调用模型接口进行回测
        ResponseEntity<PredictResponse> predictResponseResponseEntity = restTemplate.postForEntity("http://127.0.0.1:8000/predict", predictModule, PredictResponse.class);
        PredictResponse body = predictResponseResponseEntity.getBody();
        if(body!=null){
            if(!StringUtils.isEmpty(body.getResultCode())){
                PredictInfoDto predictInfoDto = new PredictInfoDto();
                predictInfoDto.setPredictCreateat(new Date());
                predictInfoDto.setCurrencyName(split[0]);
                predictInfoDto.setCurrentClosePrice(tradingData.getClosingPrice());
                predictInfoDto.setPredictCloseprice(body.getPredictCloseprice());
                predictInfoDto.setPredictClosetime(Date.from(addTimeOption(LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(split[3])), ZoneId.systemDefault()), split[1]).atZone(ZoneId.systemDefault()).toInstant()));
                predictInfoDto.setPredictResult(
                        !StringUtils.isEmpty(body.getPredictResult())
                                ? (body.getPredictResult().equals("1") ? "上涨" : "下跌")
                                : ""
                );

                predictInfoDto.setPredictBatchNum(batchId);
                predictInfoDto.setPredictUniqueTag(kindId);
                predictInfoDto.setPeriodDimension(split[1]);
                predictInfoDto.setResultCode(body.getResultCode());
                result.add(predictInfoDto);
            }
        }
    }


    public static LocalDateTime addTimeOption(LocalDateTime dateTime, String optionValue) {
        // 解析选项值中的时间单位和数量
        char timeUnit = optionValue.charAt(optionValue.length() - 1);
        int amount = Integer.parseInt(optionValue.substring(0, optionValue.length() - 1));

        // 根据时间单位添加相应的时间
        switch (timeUnit) {
            case 'm': // 分钟
                return dateTime.plus(amount, ChronoUnit.MINUTES);
            case 'h': // 小时
                return dateTime.plus(amount, ChronoUnit.HOURS);
            case 'd': // 天
                return dateTime.plus(amount, ChronoUnit.DAYS);
            case 'w': // 周
                return dateTime.plus(amount * 7, ChronoUnit.DAYS);
            case 'M': // 月
                return dateTime.plus(amount, ChronoUnit.MONTHS);
            default:
                throw new IllegalArgumentException("Invalid option value: " + optionValue);
        }
    }
}
